//订阅器，依赖收集者
class Dep {
  constructor() {
    this.deps = [];
  }
  addDep(dep) {
    this.deps.push(dep);
  }
  notify() {
    this.deps.forEach((dep) => dep.update());
  }
}
//发布者
class Watcher {
  
  constructor(vm, key, cb) {
    console.log("触发了吗1");
    this.vm = vm;
    this.key = key;
    this.cb = cb;
    //将当前watcher实例指定到Dep静态属性target
    Dep.target = this;
    this.vm[this.key]; // 触发getter 添加依赖
    Dep.target = null;
  }
  update() {
    console.log("触发了吗");
    this.cb.call(this.vm, this.vm[this.key]);
  }
}
// 指令解析类
class Complier {
  constructor(el, vm) {
    debugger;
    this.el = isNodeType(el) ? el : document.querySelector(el);
    //获取所有数据源
    this.$vm = vm;
    // 获取文档碎片对象，放入内存。减少重绘和回流
    this.fragment = nodeFragment(this.el);
    this.observe(this.$vm.data);
    // console.log('fragment',fragment)
    this.compile(this.fragment);
    //追加子元素到根元素
    this.el.appendChild(this.fragment);
  }
  //数据劫持
  observe(value) {
    if (!value || typeof value !== "object") {
      return;
    }
    //遍历该对象
    Object.keys(value).forEach((key) => {
      this.defineReactive(value, key, value[key]);
      //代理data中的属性到vue实例上
      this.proxyData(key);
    });
  }
  defineReactive(data, key, value) {
    console.log("value：", data, key, value);
    const dep = new Dep();
    // console.log("11111111", Dep.target);
    Object.defineProperty(data, key, {
      get() {
        //获取数据
        Dep.target && dep.addDep(Dep.target);
        return value;
      },
      set(newValue) {
        if (newValue === value) {
          return;
        }
        value = newValue;
        dep.notify();
      },
    });
  }
  proxyData(key) {
    Object.defineProperty(this, key, {
      get() {
        return this.$vm.data[key];
      },
      set(newVal) {
        this.$vm.data[key] = newVal;
      },
    });
  }
  // 指令，dom解析
  compile(el) {
    const childNodes = el.childNodes;
    // console.log("查看节点：", childNodes);
    Array.from(childNodes).forEach((node) => {
      if (isNodeType(node)) {
        //操作指令替换值
        const attrs = node.attributes;
        Array.from(attrs).forEach((attr) => {
          //指令的名字
          const attrName = attr.name;
          //指令对应的值
          const exp = attr.value;
          if (this.isDirective(attrName)) {
            const dir = attrName.substring(2);
            this[dir] && this[dir](node, this.$vm, exp);
          }
        });
      } else if (isInterpolation(node)) {
        //{{text}}解析
        // console.log("插值表达式：", node);
        this.compileText(node);
      }
      // 递归子节点(ul>li这种情况)
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }
  // 编译文本节点
  compileText(node) {
    // RegExp.$1 获取正则表达式中第一个()分组匹配到的值
    // 将变量值复制给DOM并展示
    node.textContent = this.$vm.data[RegExp.$1];
  }
  //指令解析器
  isDirective(attr) {
    return attr.indexOf("v-") == 0 || attr.indexOf(":") == 0;
  }
  // v-text指令值更新
  text(node, vm, exp) {
    this.update(node, vm, exp, "text");
  }
  //v-html指令
  html(node, vm, exp) {
    this.update(node, vm, exp, "html");
  }
  //v-model
  model(node, vm, exp) {
    this.update(node, vm, exp, "model");
  }
  textUpdater(node, value) {
    node.innerText = value;
  }
  htmlUpdater(node, value) {
    node.innerHTML = value;
  }
  modelUpdater(node, value) {
    node.value = value;
  }
  //更新函数
  update(node, vm, exp, dir) {
    const updateFn = this[dir + "Updater"];
    //初始化
    updateFn && updateFn(node, vm.data[exp]);
    //依赖收集
    new Watcher(vm, exp, function (value) {
      updateFn && updateFn(node, value);
    });
  }
}

function nodeFragment(node) {
  const virDom = document.createDocumentFragment();
  let firstChild;
  while ((firstChild = node.firstChild)) {
    virDom.appendChild(firstChild);
  }
  return virDom;
}

//判断是否为{{}}这种情况
function isInterpolation(node) {
  return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
// 判断是否为元素节点
function isNodeType(node) {
  return node.nodeType == 1;
}
class Mvue {
  constructor(options) {
    this.options = options;
    this.$el = this.options.el;
    this.data = this.options.data;
    if (this.$el) {
      new Complier(this.$el, this);
    }
    if (options.created) {
      options.created.call(this);
    }
  }
}
